"""
Game Objects Module
Contains all game entity classes including sprites and game objects
"""

import pygame
import random
import os

# Import constants from main game module
ASSETS_DIR = os.path.join(os.path.dirname(__file__), "Assets")
TEXTBOXES_DIR = os.path.join(ASSETS_DIR, "TextBoxes")
PLAYER_DIR = os.path.join(ASSETS_DIR, "Player")
EXPLOSION_DIR = os.path.join(ASSETS_DIR, "Explosion")


def load_textbox_images():
    """Load textbox images keyed by enemy type."""
    textbox_types = {
        "lower": "Lower Single Letter.png",
        "upper": "Upper Single Letter.png",
        "word": "Words.png",
        "phrase": "Sentence.png",
        "boss": "boss_TextBox.png",
    }

    images = {}
    for enemy_type, filename in textbox_types.items():
        path = os.path.join(TEXTBOXES_DIR, filename)
        images[enemy_type] = pygame.image.load(path).convert_alpha()
    return images


def load_player_sprites():
    """Load and return idle and shooting player sprites."""
    idle_path = os.path.join(PLAYER_DIR, "Idle.png")
    shooting_path = os.path.join(PLAYER_DIR, "Shooting.png")
    idle = pygame.image.load(idle_path).convert_alpha()
    shooting = pygame.image.load(shooting_path).convert_alpha()
    return idle, shooting


def load_explosion_frames():
    """Load explosion animation frames."""
    frames = []
    for i in range(9):
        frame_path = os.path.join(EXPLOSION_DIR, f"regularExplosion{i:02d}.png")
        frames.append(pygame.image.load(frame_path).convert_alpha())
    return frames


def create_tower_rect(logical_height):
    """Create the tower rectangle positioned for the defender."""
    tower_w, tower_h = 132, 110
    tower_rect = pygame.Rect(0, 0, tower_w, tower_h)
    tower_rect.midleft = (20, logical_height // 2)
    return tower_rect

class Explosion(pygame.sprite.Sprite):
    """
    Explosion animation sprite that plays when enemies are destroyed
    """
    
    def __init__(self, x, y, explosion_frames):
        """
        Initialize explosion at given position
        
        Args:
            x (int): X coordinate for explosion center
            y (int): Y coordinate for explosion center
            explosion_frames (list): List of explosion image frames
        """
        super().__init__()
        self.x = x
        self.y = y
        self.frame_index = 0
        self.timer = 0
        self.frame_duration = 3  # frames per explosion frame
        self.is_active = True
        self.scaled_frames = []
        
        # Scale explosion frames to logical size
        explosion_size = 80  # Fixed size for logical resolution
        for frame in explosion_frames:
            scaled = pygame.transform.smoothscale(frame, (explosion_size, explosion_size))
            self.scaled_frames.append(scaled)
        
        # Set sprite image and rect
        self.image = self.scaled_frames[0] if self.scaled_frames else pygame.Surface((1, 1))
        self.rect = self.image.get_rect(center=(x, y))
    
    def update(self):
        """Update explosion animation frame"""
        if not self.is_active:
            return
        
        self.timer += 1
        if self.timer >= self.frame_duration:
            self.timer = 0
            self.frame_index += 1
            if self.frame_index >= len(self.scaled_frames):
                self.is_active = False
                self.kill()  # Remove from sprite groups
            else:
                self.image = self.scaled_frames[self.frame_index]


class Defender(pygame.sprite.Sprite):
    """
    Player's tower defender sprite with shooting animations
    """
    
    def __init__(self, tower_rect, player_idle, player_shooting):
        """
        Initialize the defender tower
        
        Args:
            tower_rect (pygame.Rect): Tower position and size
            player_idle (pygame.Surface): Idle sprite image
            player_shooting (pygame.Surface): Shooting sprite image
        """
        super().__init__()
        self.tower_rect = tower_rect
        self.shooting_timer = 0
        self.shooting_duration = 30  # frames to show shooting animation
        self.is_shooting = False
        self.scaled_sprites = {}
        
        # Create black tower base that extends to bottom of screen
        tower_w = tower_rect.width
        tower_h = pygame.display.get_surface().get_height() - tower_rect.top
        self.tower_base = pygame.Surface((tower_w, tower_h))
        self.tower_base.fill((10, 10, 10))  # BLACK
        
        # Scale player sprites wider while reducing previous oversized scale
        scale_factor = 2.2
        player_w = int(tower_w * 1.9 * scale_factor)
        player_h = int(tower_rect.height * 1.4 * scale_factor)
        
        self.scaled_sprites['idle'] = pygame.transform.smoothscale(
            player_idle, (player_w, player_h)
        )
        self.scaled_sprites['shooting'] = pygame.transform.smoothscale(
            player_shooting, (player_w, player_h)
        )
        
        # Set current sprite
        self.image = self.scaled_sprites['idle']
        self.rect = self.image.get_rect()
        self.rect.centerx = tower_rect.centerx
        self.rect.bottom = tower_rect.top + 104  # Adjust to keep feet aligned after resizing
    
    def start_shooting(self):
        """Start shooting animation"""
        self.is_shooting = True
        self.shooting_timer = 0
        self.image = self.scaled_sprites['shooting']
    
    def update(self):
        """Update defender animation state"""
        if self.is_shooting:
            self.shooting_timer += 1
            if self.shooting_timer >= self.shooting_duration:
                self.is_shooting = False
                self.shooting_timer = 0
                # Return to idle sprite
                self.image = self.scaled_sprites['idle']
    
    def draw(self, surf):
        """
        Draw the defender including tower base
        
        Args:
            surf (pygame.Surface): Surface to draw on
        """
        # Draw black tower base extending to bottom of screen
        tower_base_rect = pygame.Rect(
            self.tower_rect.x, 
            self.tower_rect.top,
            self.tower_base.get_width(),
            self.tower_base.get_height()
        )
        surf.blit(self.tower_base, tower_base_rect)
        
        # Draw player sprite
        surf.blit(self.image, self.rect)


class Enemy(pygame.sprite.Sprite):
    """
    Enemy sprite that moves across the screen with text to type
    """
    
    def __init__(self, text, speed, color, boss=False, boss_type=1, textbox_images=None, 
                 level_manager=None, game_language="en"):
        """
        Initialize an enemy with text, movement speed, and visual properties
        
        Args:
            text (str): Text that player must type to defeat enemy
            speed (float): Movement speed
            color (tuple): RGB color for the enemy
            boss (bool): Whether this is a boss enemy
            boss_type (int): Type of boss (1-3)
            textbox_images (dict): Dictionary of textbox images
            level_manager: Reference to level manager for enemy type checking
            game_language (str): Current game language
        """
        super().__init__()
        self.text = text
        self.remaining = list(text)
        self.color = color
        self.boss = boss
        self.boss_type = boss_type
        self.level_manager = level_manager
        
        # Get screen dimensions
        screen = pygame.display.get_surface()
        WIDTH = screen.get_width()
        HEIGHT = screen.get_height()

        # Determine enemy category for sprite/scale
        if boss:
            enemy_kind = "boss"
        elif level_manager and text in level_manager.LOWER:
            enemy_kind = "lower"
        elif level_manager and text in level_manager.UPPER:
            enemy_kind = "upper"
        elif level_manager and text in level_manager.WORDS:
            enemy_kind = "word"
        else:
            enemy_kind = "word"  # fallback to word for any other case

        # Load appropriate sprite
        self._load_sprite(enemy_kind)
        
        # TextBox properties - use appropriate textbox for enemy type
        self.enemy_type = enemy_kind
        self.textbox_offset_x = 0
        self.textbox_gap = 24
        if textbox_images and enemy_kind in textbox_images:
            if self.boss:
                base_scale = 1.2
                width_multiplier = 2.85
                height_multiplier = 2.2
                self.textbox_gap = -8
            else:
                base_scale = 0.7
                width_multiplier = 2.4
                height_multiplier = 2.2
            width_scale = base_scale * width_multiplier
            height_scale = base_scale * height_multiplier
            self.textbox_img = textbox_images[enemy_kind]
            self.textbox_w = int(self.textbox_img.get_width() * width_scale)
            self.textbox_h = int(self.textbox_img.get_height() * height_scale)
            self.textbox_scaled = pygame.transform.smoothscale(self.textbox_img, (self.textbox_w, self.textbox_h))
        
        # Progressive text display
        self.text_display_timer = 0
        self.text_display_speed = 2  # characters per frame
        self.visible_text_length = 0

        # Position the enemy
        self._setup_position(WIDTH, HEIGHT)
        
        # Calculate speed for logical coordinates
        factor = 1 + (len(self.text) / 10)
        min_speed = 0.15  # Minimum speed for logical resolution
        self.speed = max(min_speed, speed / factor)

    def _load_sprite(self, enemy_kind):
        """Load and setup sprite animation frames"""
        drones_dir = os.path.join(ASSETS_DIR, "Drones")
        boss_dir = os.path.join(ASSETS_DIR, "Boss")
        
        # Map enemy types to sprite paths
        if enemy_kind == "boss":
            sprite_candidates = [
                os.path.join(boss_dir, str(self.boss_type), "Walk.png"),
                os.path.join(boss_dir, str(self.boss_type), "Idle.png"),
            ]
        else:
            sprite_map = {
                "lower": [
                    os.path.join(drones_dir, "3", "Forward.png"),
                    os.path.join(drones_dir, "3", "Idle.png"),
                ],
                "upper": [
                    os.path.join(drones_dir, "4", "Walk.png"),
                    os.path.join(drones_dir, "4", "Idle.png"),
                ],
                "word": [
                    os.path.join(drones_dir, "5_2", "Walk.png"),
                    os.path.join(drones_dir, "5_2", "Idle.png"),
                ],
            }
            sprite_candidates = sprite_map.get(enemy_kind, sprite_map["word"])

        # Load sprite sheet
        sheet_img = None
        for candidate in sprite_candidates:
            try:
                sheet_img = pygame.image.load(candidate).convert_alpha()
                break
            except pygame.error:
                continue

        # Process sprite frames
        self.frames = self._slice_spritesheet(sheet_img, enemy_kind)
        self.current_frame_index = 0
        self.anim_timer_ms = 0
        
        # Set frame duration based on enemy type
        frame_durations = {
            "lower": 80, "upper": 80,
            "word": 110, "boss": 140
        }
        self.frame_duration_ms = frame_durations.get(enemy_kind, 110)
        
        # Set initial sprite image
        self.image = self.frames[0] if self.frames else pygame.Surface((50, 50))
        self.rect = self.image.get_rect()
        self.visible_center_offset = 0
        if enemy_kind == "boss" and self.frames:
            base_frame = self.frames[0]
            visible_rect = base_frame.get_bounding_rect()
            surface_rect = base_frame.get_rect()
            self.visible_center_offset = visible_rect.centerx - surface_rect.centerx

    def _slice_spritesheet(self, sheet_surf, enemy_kind):
        """Slice spritesheet into individual frames"""
        if sheet_surf is None:
            return [pygame.Surface((50, 50))]  # Default surface
            
        sw, sh = sheet_surf.get_size()
        frame_size = sh
        if frame_size <= 0:
            return [pygame.Surface((50, 50))]
            
        num_frames = max(1, sw // frame_size)
        frames = []
        
        # Target widths by enemy type for logical resolution
        target_widths = {
            "lower": 85,
            "upper": 95,
            "word": 100,
            "boss": 300
        }
        scale_multiplier = (8 / 3) if enemy_kind == "boss" else 2.7
        target_w = int(target_widths.get(enemy_kind, 100) * scale_multiplier)
        
        for i in range(num_frames):
            frame_rect = pygame.Rect(i * frame_size, 0, frame_size, sh)
            frame = sheet_surf.subsurface(frame_rect).copy()
            
            # Scale frame
            fw, fh = frame.get_size()
            scale_factor = target_w / max(1, fw)
            target_h = int(fh * scale_factor)
            scaled = pygame.transform.smoothscale(frame, (target_w, max(1, target_h)))
            
            # Flip horizontally so sprite faces left (movement direction)
            scaled = pygame.transform.flip(scaled, True, False)
            frames.append(scaled)
        
        return frames

    def _setup_position(self, WIDTH, HEIGHT):
        """Setup initial position for the enemy"""
        if self.boss:
            # Position boss sprite with bottom touching the bottom of the screen
            self.rect.bottomleft = (WIDTH, HEIGHT)
            
            # Create separate hitbox for boss collision detection
            self.collision_rect = pygame.Rect(0, 0, self.textbox_w, self.textbox_h)
            self.textbox_offset_x = 0  # Horizontal offset if manual tweaks are needed
            visual_center = self.rect.centerx + self.visible_center_offset
            textbox_x = visual_center - self.textbox_w // 2 + self.textbox_offset_x
            textbox_y = self.rect.top - self.textbox_h - self.textbox_gap  # Use textbox_gap for vertical spacing
            self.collision_rect.topleft = (textbox_x, textbox_y)
        else:
            # For regular enemies, collision matches sprite
            min_y = 140  # Fixed values for logical resolution
            max_y = HEIGHT - 200
            self.rect.midleft = (WIDTH, random.randint(min_y, max_y))
            self.collision_rect = self.rect.copy()

            if hasattr(self, "textbox_h"):
                safe_ui_bottom = 120
                textbox_top = self.rect.top - self.textbox_h + 4
                if textbox_top < safe_ui_bottom:
                    adjustment = safe_ui_bottom - textbox_top
                    new_centery = self.rect.centery + adjustment
                    self.rect.centery = max(min(new_centery, max_y), min_y)
                    self.collision_rect = self.rect.copy()

    def move(self, dt_ms=None):
        """
        Move the enemy leftward across the screen
        
        Args:
            dt_ms (float): Delta time in milliseconds for frame-independent movement
        """
        if self.boss and dt_ms is not None:
            # dt_seconds = dt_ms / 1000.0
            # movement = self.speed * dt_seconds
            movement = 1.15  # Normalize to ~60 FPS
            self.rect.x -= movement
            if hasattr(self, 'collision_rect'):
                self.collision_rect.x -= movement
        else:
            self.rect.x -= self.speed
            if hasattr(self, 'collision_rect'):
                self.collision_rect.x -= self.speed

    def update(self, dt_ms=16):
        """
        Update enemy animation and text display
        
        Args:
            dt_ms (float): Delta time in milliseconds
        """
        # Advance animation timer
        self.anim_timer_ms += dt_ms
        while self.anim_timer_ms >= self.frame_duration_ms:
            self.anim_timer_ms -= self.frame_duration_ms
            self.current_frame_index = (self.current_frame_index + 1) % len(self.frames)
            self.image = self.frames[self.current_frame_index]
        
        # Progressive text display
        self.text_display_timer += 1
        if self.text_display_timer >= self.text_display_speed:
            self.text_display_timer = 0
            if self.visible_text_length < len(self.remaining):
                self.visible_text_length += 1

    def draw_textbox(self, surf):
        """
        Draw the enemy's textbox with current text
        
        Args:
            surf (pygame.Surface): Surface to draw on
        """
        if not hasattr(self, 'textbox_scaled'):
            return
            
        # Position textbox
        if self.boss:
            visual_center = self.rect.centerx + self.visible_center_offset
            textbox_x = visual_center - self.textbox_w // 2 + self.textbox_offset_x
            textbox_y = self.rect.top - self.textbox_h - self.textbox_gap
        else:
            textbox_x = self.rect.centerx - self.textbox_w // 2
            textbox_y = self.rect.top - self.textbox_h + 4  # Fixed offset for logical resolution

        textbox_y = max(10, textbox_y)
        if self.boss and hasattr(self, 'collision_rect'):
            self.collision_rect.x = textbox_x
            self.collision_rect.y = textbox_y
        
        surf.blit(self.textbox_scaled, (textbox_x, textbox_y))
        
        # Draw text
        visible_text = "".join(self.remaining[:self.visible_text_length])
        if visible_text:
            font_size = 35 if not self.boss else 38  # Boss text slightly larger than normal enemies
            textbox_font = pygame.font.SysFont("consolas", font_size)
            
            if self.boss:
                self._draw_multiline_text(surf, visible_text, textbox_font, textbox_x, textbox_y)
            else:
                txt = textbox_font.render(visible_text, True, (10, 10, 10))  # BLACK
                txt_x = textbox_x + (self.textbox_w - txt.get_width()) // 2
                txt_y = textbox_y + (self.textbox_h - txt.get_height()) // 2
                surf.blit(txt, (txt_x, txt_y))

    def _draw_multiline_text(self, surf, text, font, textbox_x, textbox_y):
        """Draw text split into multiple lines for boss enemies"""
        words = text.split()
        lines = []
        current_line = []
        
        for word in words:
            test_line = " ".join(current_line + [word])
            if font.size(test_line)[0] <= self.textbox_w - 20:  # Fixed margin for logical resolution
                current_line.append(word)
            else:
                if current_line:
                    lines.append(" ".join(current_line))
                current_line = [word]
        if current_line:
            lines.append(" ".join(current_line))
        
        # Render each line
        total_height = sum(font.size(line)[1] for line in lines)
        y_start = textbox_y + (self.textbox_h - total_height) // 2
        
        for i, line in enumerate(lines):
            txt = font.render(line, True, (10, 10, 10))  # BLACK
            txt_x = textbox_x + (self.textbox_w - txt.get_width()) // 2
            txt_y = y_start + i * font.size(line)[1]
            surf.blit(txt, (txt_x, txt_y))

    def hit(self, char):
        """
        Process a character hit from player input
        
        Args:
            char (str): Character typed by player
            
        Returns:
            bool: True if character was correct, False otherwise
        """
        # Skip over any '\n' characters in remaining text
        while self.remaining and self.remaining[0] == '\n':
            self.remaining.pop(0)
        if self.remaining and self.remaining[0] == char:
            self.remaining.pop(0)
            self.visible_text_length = max(0, self.visible_text_length - 1)
            return True
        return False

    def is_dead(self):
        """
        Check if enemy has been completely typed
        
        Returns:
            bool: True if all text has been typed
        """
        return len(self.remaining) == 0

    def get_collision_rect(self):
        """
        Get the rectangle used for collision detection
        
        Returns:
            pygame.Rect: Collision rectangle
        """
        return getattr(self, 'collision_rect', self.rect)